<?php
/**
 * [ActiveRecord] 模型查询类,构造出查询SQL
 * 
 * @version $Id: Select.php 421 2013-01-31 07:37:36Z linsir123 $
 * @package Mysql
 */
class ArSelect
{
	/**
	 * 数据表替换
	 */
	const AS_TABLE = '`a`';
	
	/**
	 * @var 初始化查询结构,按`Select`语句进行拆分
	 * @static 
	 */
	private static $__partsInit = array(
		'AGGREGATION' => null,
		'DISTINCT' => false,
		'FIELD' => array(),
		'FROM' 	=> array(),
		'WHERE' => null,
		'GROUP' => null,
		'ORDER' => array(),
		'LIMIT' => array()
	);
	
	/**
	 * @var 数据表别名
	 */
	public $asTable;
	
	/**
	 * @var 是否使用子查询
	 */
	public $fromSub = false;
	
	/**
	 * @var 当前查询的连接次数
	 * 	// 构造完SQL后需要清空
	 */
	public $joinTime = 0;
	
	/**
	 * @var 查询结构数组
	 * 	// 表连接时,用于中转
	 */
	public $joinPart;
	
	/**
	 * @var 查询结构数组
	 */
	public $parts;
	
	/**
	 * @var `数据库表名`
	 */
	public $table;
	
	/**
	 * @var 查询条件对象
	 */
	public $oCondition;
	
	/**
	 * 构造函数
	 * 
	 * @param string $table 需要查询的数据库表名
	 * @param ArCondition $oCondition 查询条件对象
	 */
	public function __construct($table, & $oCondition)
	{
		$this->table = $table;
		$this->oCondition = $oCondition;
		
		///
		$this->clean();
	}
	
	/**
	 * 返回对象对应的SQL语句
	 * 
	 * @return string
	 */
	public function __toString()
	{
		$sql = 'SELECT';
		foreach ($this->parts as $key => $value) {
			$method = '_to'.ucfirst($key);
			if (method_exists($this, $method)) {
				$string = call_user_func_array(array($this, $method), array($value));
				empty($string) or $sql .= ' '.$string;
			}
		}
		
		///
		$this->clean(); 
		//pr($sql);
		return $sql;
	}
	
	/**
	 * 检测某个键是否设置
	 * 
	 * @param string $key 键名
	 * @return bool
	 */
	public function hadSet($key)
	{
		return ! empty($this->parts[$key]);
	}
	
	/**
	 * 设置`SELECT`数组的某一个键的内容(覆盖)
	 * 
	 * @param string $key 键名
	 * @param mixed $value 内容
	 */
	public function set($key, $value)
	{
		$this->parts[$key] = $value;
	}
	
	/**
	 * 设置`SELECT`数组的某一个键的内容(累加)
	 * 
	 * @param string $key 键名
	 * @param mixed $value 内容
	 */
	public function push($key, $value)
	{
		array_push($this->parts[$key], $value);
	}
	
	/**
	 * 聚合查询(在某个字段上进行聚合的统一入口)
	 * 
	 * @param string $type 聚合方式
	 * @param string $field 字段
	 */
	public function setAggregation($type, $field)
	{
		$field = empty($field) ? '*' : lpString::dbQuote($field);
		
		///
		$this->set('FIELD', null);
		$this->set('AGGREGATION', "{$type}({$field})");
	}
	
	/**
	 * 重新设置操作的数据表名
	 * 
	 * @param string $table 需要查询的数据库表名
	 */
	public function setTable($table)
	{
		$this->table = $table;
	}
	
	/**
	 * 重置查询对象
	 * // 1、初妈化查询数组结构
	 * // 2、清空条件对象
	 */
	public function clean()
	{
		$this->joinTime = 0;
		$this->joinPart = array();
		$this->parts = self::$__partsInit;
		$this->asTable = null;
		$this->fromSub = false;
		
		///
		$this->oCondition->clean();
	}
	
	/**
	 * 表连接时,将数据进行合并
	 * 
	 * @param ArSelect $oSelect 被连接的对象
	 * @param string $type 连接类型
	 * @param string $key 主键名
	 * @param string $fKey 外键名
	 */
	public function join($oSelect, $type, $key, $fKey)
	{
		list($asTableThis, $asTable) = $this->_getJoinAsList($oSelect);
		$this->push('FROM', " {$type} {$oSelect->table} AS {$asTable} ON 
			{$asTableThis}.`{$key}`={$asTable}.`{$fKey}` ");
		
		///
		$this->oCondition->join($oSelect->oCondition, $asTable);
		
		foreach (array('FIELD', 'ORDER') as $k) 
			$this->joinPart[$k][] = array($oSelect->parts[$k], $asTable);
			
		///
		if ($oSelect->joinTime > 0) {
			$tmp = $oSelect->parts['FROM'];
			foreach ($tmp as $v) 
				$this->push('FROM', $v);
				
			///
			foreach ($oSelect->joinPart as $k => $v) {
				if (empty($this->joinPart[$k])) 
					$this->joinPart[$k] = $v;
				else 
					$this->joinPart[$k] = array_merge($this->joinPart[$k], $v);
			}
		}
		
		///
		$oSelect->clean();
	}
	
	/**
	 * 构造子查询
	 * 
	 * @param ArSelect $oSelect 查询对象
	 */
	public function fromSub($oSelect)
	{
		$from = $oSelect->__toString();
		
		///
		$this->fromSub = true;
		$this->asTable = '`b`';
		$this->push('FROM', "({$from}) AS {$this->asTable}");
	}
	
	
	/// 以下为私有方法 ///
	
	
	/**
	 * 取得默认的数据表别名
	 */
	private function _getAsTable($default = null)
	{
		if ( ! empty($default)) 
			return $default;
			
		///
		return empty($this->asTable) ? self::AS_TABLE : $this->asTable;
	}
	
	/**
	 * 取得当前的数据表别名列表
	 */
	private function _getJoinAsList($oSelect)
	{
		static $list = array('`b`', '`c`', '`d`');
		
		if ($oSelect->joinTime > 0) {
			$this->joinTime = $oSelect->joinTime;
			$this->asTable = $this->oCondition->asTable = $list[$this->joinTime++];
			$asTable = self::AS_TABLE;
		} else 
			$asTable = $list[$this->joinTime++];
			
		///
		return array($this->_getAsTable(), $asTable);
	}
	
	/**
	 * 构造查询条件
	 */
	private function _toWhere()
	{
		return $this->oCondition->__toString();
	}
	
	/**
	 * 构造重复设置
	 */
	private function _toDistinct($data)
	{
		return ( ! $data) ? '' : 'DISTINCT';
	}
	
	/**
	 * 构造聚合
	 */
	private function _toAggregation($data)
	{
		return empty($data) ? '' : $data;
	}
	
	/**
	 * 构造查询表
	 */
	private function _toFrom($data)
	{
		if ( ! $this->fromSub) {
			$from = "{$this->table} AS ".$this->_getAsTable();
			array_unshift($data, $from);
		}
		
		///
		return 'FROM '.implode(' ', $data);
	}
	
	/**
	 * 构造查询区间
	 */
	private function _toLimit($data)
	{
		if (empty($data)) 
			return '';
			
		///
		list($start, $offset) = $data;
		return "LIMIT {$start},{$offset}";
	}
	
	/**
	 * 构造查询字段
	 * 
	 * 数据结构如：
	 * @example array(
			'`talbe`.id',
			'username,password',
			'position as p,count(`id`) as `count`',
		)
	 */
	private function _toField($data)
	{
		if ( ! empty($this->parts['AGGREGATION'])) 
			return '';
			
		///
		$data = $this->_formatField($data);
		if (is_array($this->joinPart['FIELD'])) {
			foreach ($this->joinPart['FIELD'] as $v) {
				list($tmp, $asTable) = $v;
				
				///
				$tmp = $this->_formatField($tmp, $asTable);
				$data = array_merge($data, $tmp);
			}
		}
		
		return implode(', ', $data);
	}
	
	/**
	 * 构造查询排序
	 * 
	 * 数据结构如：
	 * @example array(
			array('id', 3),
			array('id', 0),
			array('`table`.`id` desc', 2),
		)
	 */
	private function _toOrder($data)
	{
		$list = $indexes = array();
		foreach ($data as $v) {
			$v[] = null; $list[] = $v; $indexes[] = $v[1];
		}
		if (is_array($this->joinPart['ORDER'])) {
			foreach ($this->joinPart['ORDER'] as $data) {
				list($data, $asTable) = $data;
				foreach ($data as $v) {
					$v[] = $asTable; $list[] = $v; $indexes[] = $v[1];
				}
			}
		}
		
		///
		$data = array();
		array_multisort($indexes, SORT_DESC, $list);
		foreach ($list as $v) {
			list($field, , $preTable, $asTable) = $v;
			$tmp = $this->_formatOrder($field, $asTable, $preTable);
			if ( ! empty($tmp)) 
				$data[] = $tmp;
		}
		
		///
		if (empty($data)) 
			return '';
			
		return "ORDER BY ".implode(',', $data);
	}
	
	/**
	 * 构造查询聚合
	 * 
	 * 数据结构如：
	 * @example "`table`.`id`"
	 * @example "`table`.id"
	 * @example "table.`id`"
	 * @example "`id`"
	 * @example "id, name"
	 */
	private function _toGroup($data)
	{
		$data = $this->_formatGroup($data);
		if (empty($data)) 
			return '';
			
		return "GROUP BY ".$data;
	}
	
	/**
	 * FIELD,解析
	 */
	private function _formatField($data, $asTable = null)
	{
		$asTable = $this->_getAsTable($asTable);
		
		///
		if (empty($data)) 
			return array($asTable.'.*');
			
		/// "`table`.`position` as p"
		$pattern1 = "/^(`?(\w+)`?\.)?`?(\w+)`?( AS `?(\w+)`?)?$/i";
		
		/// "count(`table`.`id`) as `count`"
		$pattern2 = "/^(\w+)\((`?(\w+)`?\.)?`?(\w+)`?\)( AS `?(\w+)`?)?$/i";
		
		///
		$columns = array();
		foreach ($data as $v) {
			$fields = explode(',', $v);
			foreach ($fields as $field) {
				$field = trim($field);
				if (preg_match($pattern1, $field, $m)) {
					
					///
					$field = $asTable.'.'.lpString::dbQuote($m[3]);
					if (isset($m[5]) && ! empty($m[5])) 
						$field .= ' AS '.lpString::dbQuote($m[5]);
						
					$columns[] = $field;
					
				} elseif (preg_match($pattern2, $field, $m)) {
					
					///
					$field = strtoupper($m[1]);
					$field .= '('.$asTable.'.'.lpString::dbQuote($m[4]).')';
					if (isset($m[6]) && ! empty($m[6])) 
						$field .= ' AS '.lpString::dbQuote($m[6]);
						
					$columns[] = $field;
					
				}
			}
		}
		
		return $columns;
	}
	
	/**
	 * ORDER,解析
	 */
	private function _formatOrder($data, $asTable = null, $preTable = true)
	{
		if ($preTable) 
			$asTable = $this->_getAsTable($asTable).'.';
		else 
			$asTable = '';
			
		///
		$pattern = "/^(`?(\w+)`?\.)?`?(\w+)`?( (asc|desc))?$/i";
		
		///
		if (preg_match($pattern, $data, $m)) 
			return $asTable.
				lpString::dbQuote($m[3]).
				' '.($m[5] ? strtoupper($m[5]) : "ASC");
				
		return '';
	}
	
	/**
	 * GROUP,解析
	 */
	private function _formatGroup($data, $asTable = null)
	{
		$asTable = $this->_getAsTable($asTable);
		
		///
		$pattern = "/^(`?(\w+)`?\.)?`?(\w+)`?$/i";
		
		///
		$columns = array();
		$fields = explode(',', $data);
		foreach ($fields as $field) {
			if (preg_match($pattern, $field, $m)) 
				$columns[] = $asTable.
					'.'.lpString::dbQuote($m[3]);
		}
		
		return implode(',', $columns);
	}
}
